#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

static int major;
static dev_t devnum;
static int read_flag = 0;

static struct class* cls = NULL;
static struct device* dev;

static int hello_open(struct inode* i_node, struct file* f)
{
    printk(KERN_ALERT "hello open\r\n");
    read_flag = 0;
    return 0;
}

static int hello_close(struct inode* i_node, struct file* f)
{
    printk(KERN_ALERT "hello close\r\n");

    return 0;
}

static ssize_t hello_read(struct file* f, char __user* data, size_t size, loff_t* loff)
{
    printk(KERN_INFO "hello read\r\n");
    if (0 == read_flag)
    {
        read_flag = 1;
        copy_to_user(data, "hello text\r\n", 13);
        return 13;
    }
    return 0;
}

static ssize_t hello_write(struct file* f, const char __user* data, size_t size, loff_t* loff)
{
    char* data_ker = kmalloc(size, GFP_KERNEL);
    memset(data_ker, 0, size);

    if (data_ker)
    {
        copy_from_user(data_ker, data, size);
        printk(KERN_INFO "hello write:%s\r\n", data_ker);
        kfree(data_ker);
        return size;
    }

    return -1;
}

struct file_operations hello_ops = {
    .open = hello_open,
    .release = hello_close,
    .read = hello_read,
    .write = hello_write,
};

struct cdev hello_dev =
{
    .owner = THIS_MODULE,
};

static void clean_up(void)
{
    cdev_del(&hello_dev);
    unregister_chrdev_region(devnum, 1);
    device_destroy(cls, devnum);
    class_destroy(cls);
}

static int __init hello_init(void)
{
    int ret = 0;
    int major, minor;
    printk(KERN_ALERT "hello module init\r\n");
    ret = alloc_chrdev_region(&devnum, 1, 1, "test driver");
    if (ret < 0)
    {
        printk(KERN_INFO "cdev_add fail: %d\r\n", ret);
        return ret;
    }

    major = MAJOR(devnum);
    minor = MINOR(devnum);

    cdev_init(&hello_dev, &hello_ops);

    ret = cdev_add(&hello_dev, devnum, 1);

    if (ret < 0)
    {
        printk(KERN_INFO "cdev_add fail: %d\r\n", ret);
        return ret;
    }

    if (NULL == cls)
    {
        cls = class_create(THIS_MODULE, "udev_class");
        if (IS_ERR(cls))
        {
            printk(KERN_ALERT "cls create fail\r\n");
            clean_up();
            return -1;
        }
    }

    dev = device_create(cls, NULL, devnum, NULL, "udev_test");

    if (IS_ERR(dev))
    {
        printk(KERN_ALERT "dev create fail\r\n");
        clean_up();
        return -1;
    }

    printk(KERN_INFO "get major, minor: %d, %d\r\n", major, minor);
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "hello module exit\r\n");
    clean_up();
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_DESCRIPTION("test module");
MODULE_AUTHOR("zzh");
MODULE_LICENSE("GPL");
